package com.onpositive.wiki3.db.catmodel;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;

import com.carrotsearch.hppc.IntOpenHashSet;
import com.onpositive.compactdata.TwoIntToByteMap;
import com.onpositive.wiki3.db.IWikiDB;
import com.onpositive.wiki3.db.impl.WikiDBImpl;

public class Categories implements Iterable<Category> {

	protected HashMap<Integer, Category> allCats = new HashMap<Integer, Category>(
			1000 * 12000);

	protected HashMap<String, TwoIntToByteMap> weigths = new HashMap<String, TwoIntToByteMap>();
	
	

	private IWikiDB db;

	public Categories(IWikiDB db) {
		this.db = db;
	}

	public void unregisterChild(int parent, int child) {
		Category category = allCats.get(parent);
		category.removeChild(child);
		Category category2 = allCats.get(child);
		category2.removeParent(parent);
	}

	public void registerChild(int parent, int child) {
		if (parent == child) {
			return;
		}
		Category parentCat = getOrCreate(parent);

		Category childCat = getOrCreate(child);
		childCat.addParent(parent);
		parentCat.addChild(child);
		// if (!parentCat.hasChild(child)) {
		// throw new IllegalStateException();
		// }
		// if (!childCat.hasParent(parent)) {
		// throw new IllegalStateException();
		// }
	}

	static class CPair {
		int m;
		int id;

		public CPair(int m, int id, int i) {
			super();
			this.m = m;
			this.id = id;
			this.i = i;
		}

		int i;

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + i;
			result = prime * result + id;
			result = prime * result + m;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			CPair other = (CPair) obj;
			if (i != other.i)
				return false;
			if (id != other.id)
				return false;
			if (m != other.m)
				return false;
			return true;
		}

	}

	static HashMap<CPair, Boolean> ms = new HashMap<Categories.CPair, Boolean>();

	public boolean hasParent(Category m, int id, int i) {
		// CPair cPair = new CPair(m.id, id, i);
		// if (ms.containsKey(cPair)) {
		// return ms.get(cPair);
		// }
		boolean innerHP = innerHP(m, id, i);
		// ms.put(cPair, innerHP);
		return innerHP;
	}

	protected boolean innerHP(Category m, int id, int i) {

		if (m.hasParent(id)) {
			return true;
		}

		if (i > 1) {
			if (m.parents != null) {
				int[] parents2 = m.getParents();
				for (int q : parents2) {

					if (hasParent(allCats.get(q), id, i - 1)) {
						return true;
					}
				}
			} else {
				if (m.pId != -1) {
					Category category = allCats.get(m.pId);
					if (hasParent(category, id, i - 1)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	public Category getOrCreate(int parent) {
		Category category = allCats.get(parent);
		if (category == null) {
			category = new Category(parent, this);
			allCats.put(parent, category);
		}
		return category;
	}

	@Override
	public Iterator<Category> iterator() {
		return allCats.values().iterator();
	}

	public int size() {
		return allCats.size();
	}

	public void accept(ICategoryMatcher m) {
		Category[] all = all();
		int a = 0;
		for (Category c : all) {
			if (allCats.containsKey(c.id)) {
				m.accept(c);
			}
			if (a % 1000 == 0) {
				System.out.println(">>" + a);
			}
			a++;
		}
	}

	public Categories(IWikiDB db, File f, boolean notm) {
		this.db = db;
		try {
			DataInputStream d = new DataInputStream(new BufferedInputStream(
					new FileInputStream(f)));
			IntOpenHashSet mm = new IntOpenHashSet();
			try {

				int lc = -1;

				while (true) {
					if (notm) {
						int readInt = d.readInt();
						int readInt1 = d.readInt();
						registerChild(readInt, readInt1);
					} else {
						int readInt = d.readInt();
						// sz++;
						if (readInt < 0) {
							lc = -readInt;
							getOrCreate(lc);
							// count++;
						} else {
							if (lc > 0) {
								mm.add(lc);
								// rc++;
								registerChild(lc, readInt);
							}
						}
					}
				}
			} catch (EOFException e) {
				// System.out.println(sz);
				// System.out.println(rc);
				// System.out.println(count);
				// System.out.println(mm.size());
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public Categories() {

	}

	public void removeAllContaining(IWikiDB db, String title) {
		IntOpenHashSet toR = new IntOpenHashSet();
		for (Category c : allCats.values()) {

			if (db.getTitleMapper().getTitle(c.id).toLowerCase()
					.contains(title)) {
				toR.add(c.id);
			}
		}
		for (int i : toR.toArray()) {
			removeCategory(i);
		}
	}

	public void validateIntegrity() {
		for (Category c : allCats.values()) {

			for (int z : c.getParents()) {
				if (!allCats.containsKey(z)) {
					throw new IllegalStateException();
				}
				Category category = allCats.get(z);
				if (!category.hasChild(c.id)) {
					throw new IllegalStateException();
				}
			}
			for (int z : c.getChildren()) {
				if (!allCats.containsKey(z)) {
					throw new IllegalStateException();
				}
				Category category = allCats.get(z);
				if (!category.hasParent(c.id)) {
					throw new IllegalStateException();
				}
			}
		}
		for (Integer i : allCats.keySet()) {
			Category category = allCats.get(i);
			if (category.id != i) {
				throw new IllegalStateException();
			}
		}
	}

	public void removeCategoryWithProxy(int i) {
		Category category = allCats.get(i);
		if (category == null) {
			return;
		}
		int[] parents = category.getParents();
		for (int z : parents) {
			Category category2 = allCats.get(z);
			category2.removeChild(i);
		}
		int[] children = category.getChildren();
		for (int z : children) {
			Category category2 = allCats.get(z);
			category2.removeParent(i);
		}

		allCats.remove(i);
		for (int p : parents) {
			for (int c : children) {
				registerChild(p, c);
			}
		}
	}

	public void removeCategory(int i) {
		Category category = allCats.get(i);
		if (category == null) {
			return;
		}
		for (int z : category.getParents()) {
			Category category2 = allCats.get(z);
			if (category2 != null) {
				category2.removeChild(i);
			}
		}
		for (int z : category.getChildren()) {
			Category category2 = allCats.get(z);
			category2.removeParent(i);
		}

		allCats.remove(i);
	}

	public void replaceAll(int from, int on) {
		Category m = allCats.get(from);
		Category m1 = allCats.get(on);
		if (m == null || m1 == null) {
			return;
		}
		int[] children = m.getChildren();
		int[] parents = m.getParents();
		removeCategory(from);
		for (int q : children) {
			if (q != from) {
				registerChild(on, q);
			}
		}
		for (int q : parents) {
			if (q != from) {
				registerChild(q, on);
			}
		}
	}

	public void removeWithChildren(int id) {
		Category category = allCats.get(id);
		for (int q : category.getChildren()) {
			removeCategory(q);
		}
		removeCategory(id);
	}

	public void normalizeSmaller() {
		for (Category c : allCats.values()) {
			int[] parents = c.getParents();
			IntOpenHashSet os = new IntOpenHashSet();
			for (int z : parents) {
				if (z == c.id) {
					os.add(c.id);
				}
				for (int q : parents) {
					if (z != q && z != c.id) {
						Category c1 = getOrCreate(q);
						// Category c2=getOrCreate(z);
						if (c1.hasChild(z)) {
							os.add(q);
						}
					}
				}
			}

			for (int m : os.toArray()) {
				unregisterChild(m, c.id);
			}
		}
	}

	void write(DataOutputStream str) throws IOException {
		for (Category m : allCats.values()) {
			str.writeInt(-m.id);
			int[] children = m.getChildren();
			for (int q : children) {
				str.writeInt(q);
			}

		}
	}

	public void write(File f) throws IOException {
		DataOutputStream dataOutputStream = new DataOutputStream(
				new BufferedOutputStream(new FileOutputStream(f)));
		write(dataOutputStream);
		dataOutputStream.close();
		for (String m:weigths.keySet()){
			weigths.get(m).write(f.getAbsolutePath()+m);
		}
	}

	public Category get(int id) {
		return allCats.get(id);
		// TODO Auto-generated method stub

	}

	public Category[] all() {
		return allCats.values().toArray(new Category[allCats.size()]);
	}

	public void fillLevels(String string, WikiDBImpl wikiDBImpl) {
		int id = wikiDBImpl.getTitleMapper().getId(string);
		doFill(id);
	}

	protected void doFill(int id) {
		Category categories = get(id);
		categories.distance = 0;
		int tryL = 0;
		while (tryL < 50) {
			for (Category c : all()) {
				if (c.distance == tryL) {
					int[] children = c.getChildren();
					for (int z : children) {
						Category categories2 = get(z);
						if (categories2.distance > tryL) {
							categories2.distance = tryL + 1;
						}
					}
				}
			}
			tryL++;
		}
	}

	public IWikiDB getDb() {
		return db;
	}

	public Category get(String string) {
		int id = db.getTitleMapper().getId(string);
		if (id != -1) {
			return get(id);
		}
		return null;
	}
}
